{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 逐步理解 `<'a, F: FnMut(&'a str)>`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fn exec<'a, F>(mut f: F)\n",
    "where\n",
    "    F: FnMut(&'a str),\n",
    "{\n",
    "    // ...\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- `<'a, F>`：函数是泛型的，带一个生命周期参数 `'a` 和一个类型参数 `F`。\n",
    "- `F: FnMut(&'a str)`：要求 `F` 实现 `FnMut`，且闭包的参数是 `&'a str`（一个带 `'a` 生命周期的字符串切片）。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**生命周期参数 `'a` 是什么？**\n",
    "\n",
    "- `'a` 可以理解为“某个借用活着的区间”。\n",
    "- `&'a str` 表示“在 `'a` 这段时间里有效的字符串切片”。\n",
    "- 当你调用 `exec` 时，传给它的每一个引用参数都会有一个真实的寿命；`'a` 只是一个占位符，编译器会把它替换成那个真实寿命。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**为什么 `FnMut(&'a str)` 要写 `'a`？**\n",
    "\n",
    "因为闭包接收的是**引用类型**。引用有借用规范：不能悬垂、不能超出作用域。\n",
    " 写成 `FnMut(&'a str)` 是在说：\n",
    "\n",
    "> “闭包的参数是一个 `&str`，这个引用至少要在 `'a` 存活；闭包在 `'a` 存活期间可以安心使用它。”\n",
    "\n",
    "这让编译器知道闭包内部不会把一个即将失效的引用保留下来，避免悬垂指针。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**`'a` 如何被决定？**\n",
    "\n",
    "当你调用 `exec` 时，`'a` 根据你传入的引用来推断："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fn exec<'a, F>(mut f: F, input: &'a str)\n",
    "where\n",
    "    F: FnMut(&'a str),\n",
    "{\n",
    "    f(input);\n",
    "}\n",
    "\n",
    "fn main() {\n",
    "    let text = String::from(\"world\");\n",
    "    let slice = &text[..]; // slice 的生命周期取决于 text\n",
    "    exec(|s| println!(\"{}\", s), slice);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 这里 `slice` 的实际生命周期与 `text` 一致。\n",
    "- 调用 `exec` 时，编译器把 `'a` 推断成 `slice` 的寿命。\n",
    "- 于是闭包 `|s| ...` 被当作 `FnMut(&'a str)`，它就能安全地在函数里使用传进来的引用。\n",
    "\n",
    "如果你传的是字符串字面量 `\"hello\"`，它本身是 `'static`，编译器就把 `'a` 当成 `'static`。所以原示例里看上去没必要写 `'a`，但写出来更明确：**闭包能接受多长寿命的引用完全取决于调用者，函数本身不做假设**。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**如果省略 `'a` 会怎样？**\n",
    "\n",
    "写成 `FnMut(&str)` 也行，Rust 会进行**生命周期省略**（lifetime elision）：“这就是 `FnMut(&'_ str)`”。`'_` 是匿名生命周期，占位符。多数情况都一样用。不过显式写 `'a` 能让读者清楚看到“这个引用的寿命来自调用方”。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 怎么判断是否需要生命周期参数?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| 检查点                        | 判断是否需要显式生命周期的情形                               | 示例 / 说明                                                  |\n",
    "| ----------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |\n",
    "| 1. 函数签名里出现引用         | 只要函数参数或返回值里有 `&T` 或 `&mut T`，就要考虑生命周期。Rust 会先尝试生命周期省略规则，如果无法确定唯一的生命周期，就需要显式标注。 | `fn foo(x: &str)` 可省略；`fn foo(x: &str, y: &str) -> &str` 需显式写 `'a`，写成 `fn foo<'a>(x: &'a str, y: &'a str) -> &'a str`。 |\n",
    "| 2. 引用参数被转交给其他调用   | 把某个引用参数传入另一个函数、闭包或存储起来时，需要保证它们之间共享同一个生命周期参数。 | `fn exec<'a, F: FnMut(&'a str)>(mut f: F, input: &'a str) { f(input); }` ——闭包 `F` 与参数 `input` 使用同一个 `'a`。 |\n",
    "| 3. 返回引用                   | 返回值是引用时，必须注明它借用了哪个输入的生命周期，否则编译器无法推导。 | `fn longest<'a>(x: &'a str, y: &'a str) -> &'a str`。        |\n",
    "| 4. 泛型约束中嵌入引用         | 如 `F: FnMut(&'a str)`、`Iterator<Item = &'a T>`，一旦引用放进泛型/trait 约束里，往往需要显式生命周期参数。 | 闭包 trait 约束：`F: FnMut(&'a str)`。                       |\n",
    "| 5. 结构体/枚举字段持有引用    | 类型内部保存引用，需要在类型定义上声明生命周期参数。         | `struct Wrapper<'a> { data: &'a str }`。                     |\n",
    "| 6. 编译器报错提示缺少生命周期 | 遇到 “missing lifetime specifier” 或 “cannot infer an appropriate lifetime” 等报错，就说明必须显式标注生命周期。 | 根据编译器提示添加 `'a` 并把相关引用连接起来。               |\n",
    "| 7. 默认 `'static` 不适用      | 若引用来自局部变量（非 `'static`），只能用泛型生命周期描述；只有全局常量、字面量等才可以用 `&'static`. | `let s = String::from(\"hi\"); let r: &str = &s;` ——必须用 `'a` 连接，不能误用 `'static`。 |\n",
    "\n",
    "**使用建议：**\n",
    "\n",
    "1. 先写最简签名，观察编译器是否能自动推断；\n",
    "2. 编译不过时，根据错误提示定位需要共享的引用，添加 `'a`；\n",
    "3. 对所有互相关联的引用使用同一个生命周期变量，并在注释中说明含义。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Rust",
   "language": "rust",
   "name": "rust"
  },
  "language_info": {
   "name": "rust"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
